package com.github.byakkili.bim.core.handler.command;

import cn.hutool.core.collection.CollUtil;
import com.github.byakkili.bim.core.BimSession;
import com.github.byakkili.bim.core.interceptor.CommandInterceptor;
import com.github.byakkili.bim.core.listener.SessionListener;
import com.github.byakkili.bim.core.support.util.BimSessionUtils;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageCodec;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.RejectedExecutionException;

/**
 * 命令请求/回复处理器
 *
 * @author Guannian Li
 */
@Slf4j
@Sharable
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class CommandHandler extends MessageToMessageCodec<CommandFrame, CommandFrame> {
    /**
     * 单例
     */
    public static final CommandHandler INSTANCE = new CommandHandler();

    @Override
    protected void decode(ChannelHandlerContext ctx, CommandFrame reqFrame, List<Object> list) {
        BimSession session = BimSessionUtils.get(ctx.channel());
        List<SessionListener> sessionListeners = session.getContext().getConfig().getSessionListeners();
        // 会话监听器
        for (SessionListener listener : sessionListeners) {
            listener.onRead(reqFrame, session);
        }
        // 线程池处理命令
        CommandRunnable task = new CommandRunnable(session, reqFrame, this);
        ExecutorService executor = session.getContext().getExecutor();
        try {
            if (executor != null) {
                executor.execute(task);
            } else {
                task.run();
            }
        } catch (RejectedExecutionException e) {
            log.error("RejectedExecutionException message = {}", e.toString(), e);
        }
    }

    @Override
    protected void encode(ChannelHandlerContext ctx, CommandFrame respFrame, List<Object> list) {
        BimSession session = BimSessionUtils.get(ctx.channel());
        List<SessionListener> sessionListeners = session.getContext().getConfig().getSessionListeners();
        // 会话监听器
        for (SessionListener listener : sessionListeners) {
            listener.onWrite(respFrame, session);
        }
        list.add(respFrame);
    }

    /**
     * 前置处理
     *
     * @param reqFrame 请求帧
     * @param session  会话
     * @return 是否往下执行
     */
    boolean applyPreHandle(CommandFrame reqFrame, BimSession session) {
        List<CommandInterceptor> interceptors = session.getContext().getConfig().getCommandInterceptors();
        if (CollUtil.isNotEmpty(interceptors)) {
            for (CommandInterceptor interceptor : interceptors) {
                if (!interceptor.preHandle(reqFrame, session)) {
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * 后置处理
     *
     * @param reqFrame  请求帧
     * @param respFrame 响应帧
     * @param session   会话
     */
    void applyPostHandle(CommandFrame reqFrame, CommandFrame respFrame, BimSession session) {
        List<CommandInterceptor> interceptors = session.getContext().getConfig().getCommandInterceptors();
        if (CollUtil.isNotEmpty(interceptors)) {
            for (int i = interceptors.size() - 1; i >= 0; i--) {
                try {
                    interceptors.get(i).postHandle(reqFrame, respFrame, session);
                } catch (Exception e) {
                    log.error("CommandInterceptor.postHandle throw exception", e);
                }
            }
        }
    }

    /**
     * 执行完后处理
     *
     * @param reqFrame 请求帧
     * @param session  会话
     * @param e        异常
     */
    void triggerAfterCompletion(CommandFrame reqFrame, BimSession session, RuntimeException e) {
        List<CommandInterceptor> interceptors = session.getContext().getConfig().getCommandInterceptors();
        if (CollUtil.isNotEmpty(interceptors)) {
            for (int i = interceptors.size() - 1; i >= 0; i--) {
                try {
                    interceptors.get(i).afterCompletion(reqFrame, session, e);
                } catch (RuntimeException re) {
                    log.error("CommandInterceptor.afterCompletion throw exception", re);
                }
            }
        }
    }
}
